Como utilizar los timers internos de un microcontrolador AVR.
En el Atmega168pa tenemos 3 timers:
NOTA: La variable F_CPU no tiene ningún efecto sobre los timers, los timers solo van contando en base a la frecuencia real del reloj y el prescaler configurado, la variable F_CPU es usada en funciones como _delay_ms para hacer el calculo del retardo.
Para configurar los timers usamos los siguientes registros:
Cada uno de los timers tiene dos modos de funcionamiento:
En el modo normal el temporizador solo va contando ticks (cada uno de los ciclos de reloj dividido por el prescaler configurado), y podemos consultar el tiempo transcurrido desde que lo hemos iniciado, el contador va aumentando hasta que desborda (ya sea el timer de 255 o de 65535).
En el modo CTC indicamos un valor con el que el timer se va comparando, cuando se alcanza dicho valor se ejecuta una acción, ya sea una interrupción o una activación de una salida, al alcanzar el valor de comparación el contador se reinicia y sigue contando hasta llegar de nuevo al valor de comparación.
Los pasos generales para usar un timer son los siguientes:
void init_timer0(){
TCCR0A |= (1 << WGM01); // Modo CTC
TCCR0B |= (1 << CS02); // Prescaler de 256
TIMSK0 |= (1 << OCIE0A); // Habilitar interrupción por OCR0A
OCR0A = 2;
sei();
}
ISR(TIMER0_COMPA_vect) {}
void init_timer1() {
TCCR1B |= (1 << WGM12); // Modo CTC
TCCR1B |= (1 << CS12) | (1 << CS10); // Prescaler 1024
TIMSK1 |= (1 << OCIE1A); // Habilitar interrupción por OCR1A
OCR1A = 976;
sei();
}
ISR(TIMER1_COMPA_vect) {}
void init_timer2(){
TCCR2A |= (1 << WGM21); // modo CTC
TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20); // Clock with /1024 prescaler
TIMSK2 |= (1 << OCIE2A); // Habilitar interrupción por OCR2A
OCR2A = 255;
sei();
}
ISR(TIMER2_COMPA_vect) {}
Para usar el modo normal (es el modo que viene activado por defecto) lo único que tenemos que hacer es establecer el preescaler.
Para establecer el preescaler a 64 en el timer1 lo haríamos así:
TCCR1B |= (1 << CS11) | (1 << CS10);
Configuraciones para los distintos preescalers:
El preescaler determinará en cuanto se divide la frecuencía principal para calcular cada tick, si ponemos el preescaler en 64 y la frecuencia es de 1 Mhz, cada tick serán 64 microsegundos (1Mhz equivale a 1 microsegundo por ciclo, si el prescaler es de 64 hay que multiplicarlo por 64 para obtener el tiempo de un tick).
Tenemos que tener en cuenta que el timer0 y el timer2 solo cuentan hasta 255 y el timer1 hasta 65535. Esto es importante porque si ponemos un preescaler muy pequeño el contador desbordará muy rapido, por ejemplo con un preescaler de 64 y una frecuencia de 1Mhz y usando el timer1 el contador desbordaría en apenas 4 segundos.
Una vez configurado el preescaler solo tenemos que leer el valor TCNTn para saber el número de ticks que han transcurrido.
Una vez leido el numero de ticks hay que tener en cuenta el preescaler configurado y la frecuencia del microcontrolador para hacer el calculo del tiempo que ha transcurrido.
Para reiniciar el contador y volver a contar desde 0 simplemente establecemos el contador a 0.
TCNT1 = 0;
Para usar el modo CTC tenemos que:
Para activar el modo CTC se hace de manera diferente según el timer:
Timer0:
TCCR0A |= (1 << WGM01);
Timer1:
TCCR1B |= (1 << WGM12);
Timer2:
TCCR2A |= (1 << WGM21);
En el modo CTC podemos usar los siguientes registros para establecer el comparador.
Cada vez que el contador llegue al comparador que hayamos establecido se disparará la acción que hayamos configurado (se explica en el siguiente paso) y se reinicia a 0 volviendo a contar de nuevo hasta el comparador.
Recordar que el numero que se establece en el comparador es el numero de ticks que va contando el timer, para saber cada cuanto tiempo se ejecuta la acción tendremos que hacer el calculo con la frecuencia y el preescaler.
Si queremos lanzar una interrupción cada vez que se alcance el valor de comparador lo configuraríamos así (para el Timer1):
TIMSK1 |= (1 << OCIE1A);
Y definiríamos la ISR() así:
ISR(TIMER1_COMPA_vect) {}
Cada uno de los timers tiene dos pines asociados sobre los que puede modificar una salida, son los siguientes:
Antes de nada tenemos que asegurarnos de que el registro DDR de los puertos que vayamos a usar están configurados como salida, en el siguiente ejemplo usamos PB1 por lo tanto sería así:
DDRB |= (1 << PB1);
Para activar la salida OC1A (PB1) del timer1 de manera que se haga un toggle del pin cada vez que se alcance el comparador se haría así:
TCCR1A |= (1 << COM1A0);
Las acciones que podemos establecer están en el datasheet, son las siguientes:
AVR | microcontrolador | timers | counters